#include <RenderManager.h>

#include <glew.h>
#include <glut.h>
#include <SDL.h>

#include <memory.h>
#include <fstream>
#include <sstream>

#include <GraphicData.h>
#include <Transform.h>
#include <RenderObject.h>
#include <Camera.h>
#include <AreaLoader.h>

inline truth IsNearCamera(const vec2& pos, const float area_size)
{
	const float nearby_scalar = 0.5f;
	Camera* c = External::camera;
	float l = (c->GetX() - c->GetW()*nearby_scalar*area_size);
	float r = (c->GetX() + c->GetW()*nearby_scalar*area_size);
	float t = (c->GetY() + c->GetH()*nearby_scalar*area_size);
	float b = (c->GetY() - c->GetH()*nearby_scalar*area_size);
	if( pos.x < l )
		return false;
	if( pos.x > r )
		return false;
	if( pos.y < b )
		return false;
	if( pos.y > t )
		return false;
	return true;
}

RenderManager::RenderManager()
:maxStorage(0)
,currentStorage(0)
,graphics(0)
,vertexShader(0)
,fragmentShader(0)
,basicProgram(0)
,glowShader(0)
,glowProgram(0)
{
	External::renderer = this;

	AllocateGraphicSpace( DEFAULT_RESERVED_GRAPHIC_SPACE );
	
	CreateShaderObjects();
}

RenderManager::~RenderManager()
{
	delete[] graphics;

	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);
	glDeleteProgram(basicProgram);
	
	glDeleteShader(glowShader);
	glDeleteProgram(glowProgram);
}

truth RenderManager::CreateShaderObjects()
{
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	basicProgram = glCreateProgram();
	
	glowShader = glCreateShader(GL_FRAGMENT_SHADER);
	glowProgram = glCreateProgram();

	if(!CreateShader("./Shaders/basic.vert", vertexShader))
		return 0;

	if(!CreateShader("./Shaders/basic.frag", fragmentShader))
		return 0;

	if(!LinkShaders(basicProgram, vertexShader, fragmentShader))
		return 0;
	
	if(!CreateShader("./Shaders/glow.frag", glowShader))
		return 0;

	if(!LinkShaders(glowProgram, 0, glowShader))
		return 0;

	return 1;
}

void RenderManager::Render(std::vector<RenderObject*>& objects)
{	
	glUseProgram( basicProgram );

	unsigned scale = glGetUniformLocation(basicProgram, "scale");
	unsigned rotation = glGetUniformLocation(basicProgram, "rotation");
	unsigned pos = glGetUniformLocation(basicProgram, "pos");
	unsigned color = glGetUniformLocation(basicProgram, "object_color");
	unsigned proj = glGetUniformLocation(basicProgram, "projectionMatrix");

	float projMat[16];
	glGetFloatv(GL_PROJECTION_MATRIX, projMat);

	glUniformMatrix4fv(proj, 1, 0, projMat);

	glEnable(GL_BLEND);
	glBlendFunc (GL_SRC_COLOR, GL_ONE);

	for(unsigned i = 0; i < objects.size(); ++i)
	{
		if( !objects[i]->visible )
			continue;

		GraphicData* gfx = objects[i]->gfx;
		unsigned numShapes = gfx->gfxPoints.size();

		glUniform1f(scale, objects[i]->transform->scale);
		glUniformMatrix2fv(rotation, 1, 0, objects[i]->transform->rot_array);
		glUniform2f(pos, objects[i]->transform->loc.x, objects[i]->transform->loc.y);
		
		for(unsigned j = 0; j < numShapes; ++j)
		{
			unsigned numVerts = gfx->dataSizes[j];

			glBindVertexArray(gfx->vao[j]);
			//std::cerr<<j<<": drawing: "<<numVerts/2 <<"\n";
			
			if( objects[i]->gfx->solid[j] )
			{
				float fancolor[4] = {
						  0.5f * objects[i]->colors[j][0], 
						  0.5f * objects[i]->colors[j][1], 
						  0.5f * objects[i]->colors[j][2], 0.5f };
				glUniform4fv(color, 1, fancolor);
				glDrawArrays(GL_TRIANGLE_FAN, 0, numVerts/2);
			}

			glUniform4fv(color, 1, objects[i]->colors[j]);
			glDrawArrays(GL_LINE_LOOP, 0, numVerts/2);
		}
	}
	
	glDisable(GL_BLEND);

	glUseProgram( 0 );
}


		
void RenderManager::RenderObjects(std::vector<RenderObject*>& objects)
{
	for(unsigned i = 0; i < objects.size(); ++i)
	{
		if( !objects[i]->visible )
			continue;

		if( !IsNearCamera( objects[i]->transform->loc, 0.5f ) )
			continue;

		for( unsigned k = 0; k < objects[i]->gfx->gfxPoints.size(); ++k)
		{
			glColor4fv( objects[i]->colors[k] );
			std::vector<vec2>& points = objects[i]->gfx->gfxPoints[k];



		glBegin(GL_LINES);

		for( unsigned j = 0; j < (-1 + points.size()); ++j )
		{
			vec2 renderpoint1 = ( objects[i]->transform->scale * points[j] ); //scale
			renderpoint1 = objects[i]->transform->rot.Solve( renderpoint1 ); //rotate
			renderpoint1 += objects[i]->transform->loc; //translate

			
			vec2 renderpoint2 = ( objects[i]->transform->scale * points[j+1] ); //scale
			renderpoint2 = objects[i]->transform->rot.Solve( renderpoint2 ); //rotate
			renderpoint2 += objects[i]->transform->loc; //translate

			glVertex2f( renderpoint1.x, renderpoint1.y ); //draw point
			glVertex2f( renderpoint2.x, renderpoint2.y ); //draw point
		}

		unsigned lastPoint = points.size()-1;
		vec2 renderpoint1 = ( objects[i]->transform->scale * points[lastPoint] ); //scale
		renderpoint1 = objects[i]->transform->rot.Solve( renderpoint1 ); //rotate
		renderpoint1 += objects[i]->transform->loc; //translate

		
		vec2 renderpoint2 = ( objects[i]->transform->scale * points[0] ); //scale
		renderpoint2 = objects[i]->transform->rot.Solve( renderpoint2 ); //rotate
		renderpoint2 += objects[i]->transform->loc; //translate

		glVertex2f( renderpoint1.x, renderpoint1.y ); //draw point
		glVertex2f( renderpoint2.x, renderpoint2.y ); //draw point
		glEnd();

			if( objects[i]->gfx->solid[k] )
			{
				glEnable(GL_BLEND);
				glBlendFunc (GL_SRC_COLOR, GL_ONE);
				//glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				glColor4f(0.5f * objects[i]->colors[k][0], 
						  0.5f * objects[i]->colors[k][1], 
						  0.5f * objects[i]->colors[k][2], 0.5f);
				glBegin(GL_TRIANGLE_FAN);
				for( unsigned j = 0; j < points.size(); ++j )
				{
					vec2 renderpoint = ( objects[i]->transform->scale * points[j] ); //scale
					renderpoint = objects[i]->transform->rot.Solve( renderpoint ); //rotate
					renderpoint += objects[i]->transform->loc; //translate
					glVertex2f( renderpoint.x, renderpoint.y );
				}
				glEnd();
				glDisable(GL_BLEND);
				continue;
			}
		}
	}
}

#if 0 //Old method of loading graphic data... remove this
void RenderManager::RenderObjects(std::vector<RenderObject*>& objects)
{
	glBegin(GL_LINES);
	for(unsigned i = 0; i < objects.size(); ++i)
	{
		if( !objects[i]->visible )
			continue;

		if( !IsNearCamera( objects[i]->transform->loc, 0.5f ) )
			continue;

		glColor4fv( objects[i]->color );
		for( unsigned j = 0; j < (-1 + objects[i]->gfx->size); ++j )
		{
			vec2 renderpoint1 = ( objects[i]->transform->scale * objects[i]->gfx->dataPoints[j] ); //scale
			renderpoint1 = objects[i]->transform->rot.Solve( renderpoint1 ); //rotate
			renderpoint1 += objects[i]->transform->loc; //translate

			
			vec2 renderpoint2 = ( objects[i]->transform->scale * objects[i]->gfx->dataPoints[j+1] ); //scale
			renderpoint2 = objects[i]->transform->rot.Solve( renderpoint2 ); //rotate
			renderpoint2 += objects[i]->transform->loc; //translate

			glVertex2f( renderpoint1.x, renderpoint1.y ); //draw point
			glVertex2f( renderpoint2.x, renderpoint2.y ); //draw point
		}

		unsigned lastPoint = objects[i]->gfx->size-1;
		vec2 renderpoint1 = ( objects[i]->transform->scale * objects[i]->gfx->dataPoints[lastPoint] ); //scale
		renderpoint1 = objects[i]->transform->rot.Solve( renderpoint1 ); //rotate
		renderpoint1 += objects[i]->transform->loc; //translate

		
		vec2 renderpoint2 = ( objects[i]->transform->scale * objects[i]->gfx->dataPoints[0] ); //scale
		renderpoint2 = objects[i]->transform->rot.Solve( renderpoint2 ); //rotate
		renderpoint2 += objects[i]->transform->loc; //translate

		glVertex2f( renderpoint1.x, renderpoint1.y ); //draw point
		glVertex2f( renderpoint2.x, renderpoint2.y ); //draw point
	}
	glEnd();
}
#endif


GraphicData* RenderManager::LoadGfx(std::string filename)
{
	if( HasGfx( filename ) )
		return graphics[ loadedGraphics[ filename ] ];

	std::fstream fs(filename.c_str(), std::ios::in);

	if( !fs.is_open() )
	{
		std::cerr<<"FILE NOT FOUND: "<<filename <<"\n";
		return 0;
	}

	GraphicData* data = new GraphicData();
	unsigned numSets = 0;
	unsigned readSets = 0;
	unsigned totalPoints = 0;
	unsigned readPoints = 0;
	fs >> numSets;

	while( fs.good() )
	{
		std::string currentSetName;
		unsigned numPoints = 0;
		std::vector< vec2 > setPoints;

		fs >> currentSetName;
		fs >> numPoints;
		totalPoints+=numPoints;

		for(unsigned i = 0; i < numPoints; ++i)
		{
			vec2 point;
			fs >> point.x;
			fs >> point.y;
			setPoints.push_back( point );
			readPoints++;
		}
		readSets++;
		data->gfxPoints.push_back( setPoints );
	}

	if( readSets != numSets )
	{
		std::cerr<<"Error gfx Load failed: numSets != amount of point sets read from file!\n";
		delete data;
		return 0;
	}

	if( readPoints != totalPoints )
	{
		std::cerr<<"Error gfx Load failed: totalPoints != amount of points read from file!\n";
		delete data;
		return 0;
	}

	data->CreateRenderData();
		
	if( !AddGfx( data, filename ) )
	{
		std::cerr<<"Error: failed to add graphic to system!\n";
		delete data;
		return 0;
	}

	return data;
}

GraphicData* RenderManager::LoadGfxFromBMP(const std::string& filename)
{
    if( HasGfx( filename ) )
		return graphics[ loadedGraphics[ filename ] ];

    //Make sure the extension is .bmp
	std::string::const_reverse_iterator ext_getter = filename.rbegin();
	std::string ext;
	for(unsigned i = 0; i < 3; ++i, ++ext_getter)
		ext.push_back( *ext_getter );

	if( ext != "pmb" )
	{
		std::cerr<<"Error: "<<filename <<" not a bitmap file.\n";
		return 0;
	}

    //load in the data
    std::list<ColorData> data;
    LoadColorDataForGfx(filename, data);

    if(data.empty())
	{
		std::cerr<<"Error: No colors found in the bitmap file.\n";
		return 0;
	}

    const unsigned H = 100;
    const unsigned W = 100;
    std::map<unsigned, std::list<ColorData> > sorted;

    std::list<ColorData>::iterator iter;
    iter = data.begin();
    for(; iter != data.end(); ++iter)
    {
        //get the index, sort them based on the index
        unsigned index = iter->g;
        sorted[ index ].push_back( *iter );
    }

    GraphicData* gfxData = new GraphicData();

    std::map<unsigned, std::list<ColorData> >::iterator iter2;
    iter2 = sorted.begin();
    for(; iter2 != sorted.end(); ++iter2)
    {
		if( iter2->second.size() <= 1 )
		{
			std::cerr<<"Error: cannot generate set of points with 1 or fewer points.\n";
			delete gfxData;
			return 0;
		}

		std::map<unsigned, vec2> sorted_points;

		//generate sorted set of points
		iter = iter2->second.begin();
		for(; iter != iter2->second.end(); ++iter)
			sorted_points[ iter->r ] = iter->loc;

		//put the sorted points into a vector
		std::vector< vec2 > setPoints;
		std::map<unsigned, vec2>::iterator set_maker;
		set_maker = sorted_points.begin();
		for(; set_maker != sorted_points.end(); ++set_maker)
			setPoints.push_back( set_maker->second );

		//put the vector in the new graphic object
		gfxData->gfxPoints.push_back( setPoints );
    }
	
	gfxData->CreateRenderData();

    if( !AddGfx( gfxData, filename ) )
    {
        std::cerr<<"Error: failed to add graphic to system!\n";
        delete gfxData;
        return 0;
    }
        
	return gfxData;
}



		
RenderObject* RenderManager::CreateRenderObject(Transform* t, GraphicData* g)
{
	RenderObject* r = new RenderObject(t,g);
	renderObjects.push_back( r );
	return r;
}



//////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////
//private data
//////////////////////////////////////////////////////////////////////////////////////

truth RenderManager::CreateShader(const std::string& filename, unsigned handle)
{	
	char* shader_data = 0;
	if( !LoadShaderFile(filename, &shader_data) )
		return 0;

	if(! CompileShader(handle, shader_data) )
		return 0;
	
	delete[] shader_data;
	return 1;
}

		
truth RenderManager::LoadShaderFile(const std::string& filename, char** out_shaderStrings)
{
	if(*out_shaderStrings)
	{
		std::cerr<<"Error: output destination must be null.\n";
		return 0;
	}

	std::fstream file;
	file.open( filename.c_str(), std::ios::in );
	if(!file.is_open())
	{
		std::cerr<<"Error: shader file "<<filename <<" not found.\n";
		return 0;
	}

	std::string file_data;
	while( file.good() )
	{
		char c = file.get();
		file_data.push_back(c); 
	}

	const unsigned data_size = file_data.size();
	*out_shaderStrings = new char[ data_size ];
	std::string::iterator iter = file_data.begin();
	for(unsigned i = 0; i < data_size && iter != file_data.end(); ++i)
	{
		(*out_shaderStrings)[i] = *iter;
		++iter;
	}
	return 1;
}

truth RenderManager::CompileShader(unsigned shaderHandle, char* shader_data)
{
	glShaderSource(shaderHandle, 1, const_cast<const GLchar**>(&shader_data), 0);
	glCompileShader(shaderHandle);

	int logLength = 0;
	glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLength);
	if( logLength > 0 )
	{
		char error_log[1024];
		glGetShaderInfoLog(shaderHandle, 1024, 0, error_log );
		std::cerr<<"\n"<<error_log;
		delete[] shader_data;
		return 0;
	}
	return 1;
}
		
truth RenderManager::LinkShaders(unsigned program, unsigned vertex, unsigned fragment)
{
	if( vertex != 0 )
		glAttachShader( program, vertex );
	glAttachShader( program, fragment );

	glLinkProgram( program );
	
	int logLength = 0;
	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
	if( logLength > 0 )
	{
		char error_log[1024];
		glGetProgramInfoLog(program, 1024, 0, error_log );
		std::cerr<<"\n"<<error_log;
		return 0;
	}
	return 1;
}


void RenderManager::LoadColorDataForGfx(const std::string& filename, std::list<ColorData >& out_data)
{
	if(filename.empty())
			return;

	SDL_Surface* data = SDL_LoadBMP( filename.c_str() );
	if(!data)
	{
		std::cerr<<"Error loading file: "<<filename <<"\n";
		return;
	}

	std::list< ColorData > colordata;

	const unsigned pixelsize = 3;

	if( data->format->BytesPerPixel != pixelsize )
	{
		std::cerr<<"Error: must be a 24-bits-per-pixel bitmap. Load failed\n";
		SDL_FreeSurface(data);
		return;
	}

	if( !(data->w%2 || data->h%2) )
	{
		std::cerr<<"Error: bitmap height and width must be odd numbers. Load failed\n";
		SDL_FreeSurface(data);
		return;
	}

	if(SDL_MUSTLOCK(data))
	{
		SDL_LockSurface( data );
	}

	//when inserting the locations, we convert the bitmap to a coordinate grid
	//then use the center of the bitmap as the origin and scale down
	//the distance between points so all points lie in a 0 to 1 range
	for(int y = 0; y < data->h; y++)
	{
		for(int x = 0; x < data->w; x++)
		{
			AreaLoader::pixeltype color[3]={0,0,0};
			memcpy(color,
			&((AreaLoader::pixeltype*)data->pixels)[(y*data->pitch) + x*pixelsize],
			sizeof(AreaLoader::pixeltype)*pixelsize);

			if((color[0] != 0 || color[1] != 0 || color[2] != 0) &&
									  !(color[0] == 255 && color[1] == 255 && color[2] == 255) )
			{
				int w = data->w - 1;
				int h = data->h - 1;
				int midx = w / 2;
				int midy = h / 2;
				float in_x = static_cast<float>(x), in_y = static_cast<float>(y);
				if(x != midx)
					in_x = (x < midx ? -(midx - in_x) : in_x - midx );
				if(y != midy)
					in_y = (y < midy ? midy - in_y : -(in_y - midy) );
				in_x /= midx;
				in_y /= midy;
				colordata.push_back(ColorData(color[2],color[1],color[0], vec2(in_x,in_y)));
			}
		}
	}

	if( SDL_MUSTLOCK( data ) )
	{
		SDL_UnlockSurface( data );
	}

	SDL_FreeSurface(data);
	out_data = colordata;
}



truth RenderManager::AddGfx( GraphicData* g, std::string filename )
{
	if( !g )
	{
		std::cerr<<"error cannot add null graphic object\n";
		return 0;
	}

	if( maxStorage == currentStorage )
	{
		AllocateGraphicSpace( maxStorage * 2 );
	}

	graphics[ currentStorage ] = g;
	loadedGraphics[ filename ] = currentStorage;

	currentStorage++;
	return 1;
}
		
truth RenderManager::HasGfx( std::string filename )
{
	if( loadedGraphics.find( filename ) != loadedGraphics.end() )
	{
		return 1;
	}
	return 0;
}

void RenderManager::AllocateGraphicSpace( unsigned size )
{
	if( size <= maxStorage )
	{
		std::cerr<<"ERROR: cannot allocate a smaller buffer than the current max size!\n";
		return;
	}

	if( graphics )
	{
		GraphicData** old_graphics = new GraphicData*[maxStorage];
		memcpy( old_graphics, graphics, sizeof( GraphicData* ) * maxStorage  );
		delete[] graphics;
		graphics = new GraphicData*[size];
		memset( graphics, 0, sizeof( GraphicData* ) * size );
		memcpy( graphics, old_graphics, sizeof( GraphicData* ) * maxStorage  );
		delete[] old_graphics;
	}
	else
	{
		graphics = new GraphicData*[size];
		memset( graphics, 0, sizeof( GraphicData* ) * size );
		currentStorage = 0;
	}
		
	maxStorage = size;
}
		
void RenderManager::RemoveRenderObject( RenderObject* r )
{
	if(!r)
		return;

	for(unsigned i = 0; i < renderObjects.size(); ++i)
	{
		if( r == renderObjects[i] )
		{
			RemoveRenderObject( i );
			return;
		}
	}
}


void RenderManager::RemoveRenderObject(unsigned i)
{
	if(i >= renderObjects.size())
		return;

	delete renderObjects[i]; 
	renderObjects[i] = 0;
	renderObjects.erase( renderObjects.begin() +  i );
}